/**
 * Copyright 2009-2011 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.myjung.rest;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.myjung.Myjung;
import net.myjung.model.Query;

import org.springframework.util.StringUtils;

/**
 * @author Steve M. Jung
 */
public abstract class AbstractRestServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	public AbstractRestServlet() {
	}

	protected abstract Object toObject(String str) throws Exception;
	protected abstract Object get(HttpServletRequest request, HttpServletResponse response, String id, Map<String, Object> options) throws Exception;
	protected abstract Object set(HttpServletRequest request, HttpServletResponse response, String username, Object obj, Map<String, Object> options)
			throws Exception;
	protected abstract Object create(HttpServletRequest request, HttpServletResponse response, String username, Object obj,
			Map<String, Object> options) throws Exception;
	protected abstract Object update(HttpServletRequest request, HttpServletResponse response, String username, Object obj,
			Map<String, Object> options) throws Exception;
	protected abstract Object delete(HttpServletRequest request, HttpServletResponse response, String id, Map<String, Object> options)
			throws Exception;
	protected abstract List<?> getList(HttpServletRequest request, HttpServletResponse response, Query conds, Map<String, Object> options)
			throws Exception;
	protected abstract void action(HttpServletRequest request, HttpServletResponse response, PrintWriter out) throws Exception;

	private Map<String, Object> populateOptions(HttpServletRequest request, HttpServletResponse response, Map<String, Object> options) {
		if (options == null)
			options = new HashMap<String, Object>();
		if (!options.containsKey("servletContext"))
			options.put("servletContext", getServletContext());
		return options;
	}
	@SuppressWarnings("unchecked")
	public static Map<String, Map<String, Object>> toObjectParameterMap(HttpServletRequest request) {
		Map<String, Map<String, Object>> paramMap = new HashMap<String, Map<String, Object>>();
		appendParameterMap(request, paramMap, request.getParameterNames());
		return paramMap;
	}
	private static void appendParameterMap(HttpServletRequest request, Map<String, Map<String, Object>> paramMap, Enumeration<String> paramNames) {
		while (paramNames.hasMoreElements()) {
			String paramName = paramNames.nextElement();
			if (paramName.indexOf("[") != -1) {
				String[] paramNameTokens = StringUtils.tokenizeToStringArray(paramName, "[");
				String name = paramNameTokens[0];
				String field = paramNameTokens[1];
				field = StringUtils.replace(StringUtils.replace(StringUtils.replace(field, "]", ""), "\"", ""), "'", "").trim();
				if (!paramMap.containsKey(name))
					paramMap.put(name, new HashMap<String, Object>());
				paramMap.get(name).put(field, request.getParameter(paramName));
			} else if (paramName.indexOf("_id") != -1) {
				String name = paramName.substring(0, paramName.indexOf("_id"));
				if (!paramMap.containsKey(name))
					paramMap.put(name, new HashMap<String, Object>());
				paramMap.get(name).put("id", request.getParameter(paramName));
			}
		}
	}

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String action = request.getParameter("action");
		boolean isAction = !Myjung.isEmpty(action);
		if (isAction && action.equals("set")) {
			doPut(request, response);
			return;
		}
		String method = request.getParameter("_method");
		if (!Myjung.isEmpty(method) && !method.equalsIgnoreCase("get")) {
			if (method.equals("post")) {
				doPost(request, response);
			} else if (method.equals("put")) {
				doPut(request, response);
			} else if (method.equals("delete")) {
				doDelete(request, response);
			} else {
				throw new ServletException("No such action:" + method + " found.");
			}
			return;
		}
		response.setContentType("text/xml");
		response.setCharacterEncoding("UTF-8");
		PrintWriter out = response.getWriter();
		if (isAction && !action.equals("load")) {
			try {
				action(request, response, out);
			} catch (ServletException e) {
				throw e;
			} catch (Exception e) {
				throw new ServletException(e);
			}
			return;
		}
		String pathInfo = request.getPathInfo();
		String id = pathInfo != null && pathInfo.length() > 1 ? pathInfo.substring(1) : null;
		if (Myjung.isEmpty(id)) {
			Query conds = (Query) new Query().toObject(Myjung.toString(request.getParameter("conds"), "<conds pageNo=\"0\" pageSize=\"10\"/>"));
			try {
				List<?> list = getList(request, response, conds, populateOptions(request, response, null));
				printList(request, response, out, list, null);
			} catch (ServletException e) {
				throw e;
			} catch (Exception e) {
				throw new ServletException(e);
			}
		} else {
			try {
				if (id.endsWith(".xml"))
					id = id.substring(0, id.indexOf(".xml"));
				Object obj = get(request, response, id, populateOptions(request, response, null));
				if (obj == null && !isAction)
					throw new ServletException("Not exist object: " + id);
				print(request, response, out, obj, null);
			} catch (ServletException e) {
				throw e;
			} catch (Exception e) {
				throw new ServletException(e);
			}
		}
	}
	protected void print(HttpServletRequest request, HttpServletResponse response, PrintWriter out, Object obj, String itemName) {
		if (obj == null) {
			out.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<empty/>");
		} else {
			out.print(Myjung.toXMLString(obj, true, itemName, null, Myjung.toString(request.getParameter("_level"), Myjung.LEVEL_ALL)));
		}
	}
	protected void printList(HttpServletRequest request, HttpServletResponse response, PrintWriter out, List<?> list, String itemName) {
		StringBuffer buf = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		if (Myjung.isEmpty(list)) {
			buf.append("\r\n<list/>");
		} else {
			buf.append("\r\n<list>");
			for (Object obj : list)
				buf.append("\r\n").append(
						Myjung.toXMLString(obj, false, itemName, "\t", Myjung.toString(request.getParameter("_level"), Myjung.LEVEL_LITE)));
			buf.append("\r\n</list>");
		}
		out.print(buf);
	}
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String action = request.getParameter("action");
		boolean isAction = !Myjung.isEmpty(action);
		if (isAction && action.equals("set")) {
			doPut(request, response);
			return;
		} else if (isAction && action.equals("load")) {
			doGet(request, response);
			return;
		}
		String method = request.getParameter("_method");
		if (!Myjung.isEmpty(method) && !method.equalsIgnoreCase("post")) {
			if (method.equals("get")) {
				doGet(request, response);
			} else if (method.equals("put")) {
				doPut(request, response);
			} else if (method.equals("delete")) {
				doDelete(request, response);
			} else {
				throw new ServletException("No such action:" + method + " found.");
			}
			return;
		}
		response.setContentType("text/xml");
		response.setCharacterEncoding("UTF-8");
		PrintWriter out = response.getWriter();
		if (isAction) {
			try {
				action(request, response, out);
			} catch (Exception e) {
				throw new ServletException(e);
			}
			return;
		}
		try {
			String username = request.getParameter("username");
			String xml = request.getParameter("objectXML");
			Object obj = toObject(xml);
			obj = create(request, response, username, obj, populateOptions(request, response, null));
			out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
			out.print(obj);
		} catch (ServletException e) {
			throw e;
		} catch (Exception e) {
			throw new ServletException(e);
		}
	}
	@Override
	protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String action = request.getParameter("action");
		boolean isAction = !Myjung.isEmpty(action);
		if (isAction && action.equals("load")) {
			doGet(request, response);
			return;
		}
		response.setContentType("text/xml");
		response.setCharacterEncoding("UTF-8");
		PrintWriter out = response.getWriter();
		if (isAction && !action.equals("set")) {
			try {
				action(request, response, out);
			} catch (Exception e) {
				throw new ServletException(e);
			}
			return;
		}
		try {
			String username = request.getParameter("username");
			String xml = request.getParameter("objectXML");
			Object obj = toObject(xml);
			Map<String, Object> options = new HashMap<String, Object>();
			options.put("level", Myjung.toString(request.getParameter("_level"), Myjung.LEVEL_FLAT));
			options = populateOptions(request, response, options);
			if (isAction) {
				obj = set(request, response, username, obj, options);
			} else {
				obj = update(request, response, username, obj, options);
			}
			out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
			out.print(obj);
		} catch (ServletException e) {
			throw e;
		} catch (Exception e) {
			throw new ServletException(e);
		}
	}
	@Override
	protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String action = request.getParameter("action");
		boolean isAction = !Myjung.isEmpty(action);
		if (isAction && action.equals("set")) {
			doPut(request, response);
			return;
		} else if (isAction && action.equals("load")) {
			doGet(request, response);
			return;
		}
		response.setContentType("text/xml");
		response.setCharacterEncoding("UTF-8");
		PrintWriter out = response.getWriter();
		if (isAction) {
			try {
				action(request, response, out);
			} catch (Exception e) {
				throw new ServletException(e);
			}
			return;
		}
		String pathInfo = request.getPathInfo();
		String id = pathInfo != null && pathInfo.length() > 1 ? pathInfo.substring(1) : null;
		if (id == null)
			throw new ServletException("Not exist object: " + id);
		try {
			if (id.endsWith(".xml"))
				id = id.substring(0, id.indexOf(".xml"));
			Object obj = delete(request, response, id, populateOptions(request, response, null));
			if (obj == null)
				throw new ServletException("Not exist object: " + id);
			out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
			out.print(obj);
		} catch (ServletException e) {
			throw e;
		} catch (Exception e) {
			throw new ServletException(e);
		}
	}
}
